/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package net.etrs.formathlon.persistance.utils;

import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import javax.persistence.TypedQuery;
import net.etrs.formathlon.entity.utils.Archivable;
import net.etrs.formathlon.entity.utils.IdentifiableEntity;

/**
 * Facade générique CRUD pour les entités, paramétré par "generics".
 * @author OPIF
 */
public abstract class AbstractFacade <T extends IdentifiableEntity> implements Facade<T>{

    @PersistenceContext
    private EntityManager em;

    private Class parameterizedType;
    
    @PostConstruct
    protected void init(){
        ParameterizedType paramType = (ParameterizedType) this.getClass().getGenericSuperclass();
        parameterizedType = (Class) paramType.getActualTypeArguments()[0];
    }

    @Override
    public T newInstance(){
        // utilisation de l'API de réflection pour instancier le bon type paramétré par le Generic
        try{
            return (T) parameterizedType.newInstance();
        }
        catch (InstantiationException ex){
            Logger.getLogger(AbstractFacade.class.getCanonicalName()).log(Level.SEVERE, null, ex);
        }
        catch (IllegalAccessException ex){
            Logger.getLogger(AbstractFacade.class.getCanonicalName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }
    
    @Override
    public Class getBusinessClass(){
        return this.parameterizedType;
    }

    @Override
    public void create(T t) throws PersistenceException{
        getEm().persist(t);
    }

    @Override
    public void update(T t) throws PersistenceException{
        getEm().merge(t);
    }

    @Override
    public void delete(T  t) throws PersistenceException{
        if(t instanceof Archivable){
            archiver(t);
        }
        else{
            // t est ici détaché
            // pour le supprimer, sans influer sur la base de données
            // il faut aller le récupérer selon son ID ..
            // cela impose que l'entity respecte l'interface "IdentifiableEntity" 
            // créée pour l'occasion.

            T attachedEntity = (T) em.find(parameterizedType, t.getId());

            // ... et demander sa suppression
            em.remove(attachedEntity);
        }
    }
    
    @Override
    public void archiver(T  t) throws PersistenceException {
        if(t instanceof Archivable){
            ((Archivable)t).setArchive(true);
            update(t);
        }
        else{
            throw new PersistenceException(String.format(
                    "L'instance de classe '%s' ne peut être archivée",
                    t.getClass().toString()));
        }
    }

    @Override
    public List<T> readAll() throws PersistenceException{
        StringBuilder strQuery=new StringBuilder("SELECT e FROM %s e");
        if(newInstance() instanceof Archivable){
            strQuery.append(" WHERE e.archive = false");
        }
        TypedQuery<T> q = em.createQuery(
                String.format(strQuery.toString(),parameterizedType.getSimpleName()),
                parameterizedType);
        return q.getResultList();
    }
    
    @Override
    public T read(Long id) throws PersistenceException{
        return (T) getEm().find(parameterizedType, id);
    }

    /**
     * @return the em
     */
    public EntityManager getEm() {
        return em;
    }
}
